/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or ( at your option ) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "commonheaders.h"
#include "classContactQueue.h"
#include "svcRefreshUserDetails.h"
#include "menuitems.h"

namespace NServices 
{
	namespace RefreshUserDetails 
	{
		#define HM_PROTOACK	( WM_USER+100 )

		typedef INT  (*PUpdCallback) ( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, PVOID UserData );

		/***********************************************************************************************************
		 * class CUpdProgress
		 ***********************************************************************************************************/

		class CUpdProgress
		{
		protected:
			BOOLEAN			_bBBCode;		// TRUE if text renderer can handle BBCodes
			BOOLEAN			_bIsCanceled;	// is set to TRUE uppon click on the CANCEL button
			PUpdCallback	_pFnCallBack;	// a pointer to a callback function, which can be used 
											// to catch several messages by the caller.
			PVOID			_pData;			// application defined data
			HWND			_hWnd;			// window handle of the progress dialog/popup

			/**
			 * This is the default window procedure, which is called for both progress dialog and popup.
			 * It handles some common messages and calls the user defined callback function if defined.
			 *
			 * @param		pProgress	- This is the pointer to the object of CUpdProgress.
			 * @param		hWnd		- HWND window handle of the progress dialog
			 * @param		uMsg		- message sent to the dialog
			 * @param		wParam		- message specific parameter
			 * @param		lParam		- message specific parameter
			 *
			 * @return	This method returns 0.
			 **/
			static LRESULT CALLBACK DefWndProc( CUpdProgress *pProgress, HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) 
			{
				__try
				{
					if( PtrIsValid( pProgress ) )
					{
						switch( uMsg )
						{
						case UM_POPUPACTION:
						case WM_COMMAND:
							{
								if( wParam == MAKEWORD( IDSKIP, BN_CLICKED ) )
								{
									pProgress->Destroy();
								}
								else						
								if( wParam == MAKEWORD( IDCANCEL, BN_CLICKED ) )
								{
									pProgress->_bIsCanceled = TRUE;
								}
							}
						}
						if( PtrIsValid( pProgress->_pFnCallBack ) )
						{
							pProgress->_pFnCallBack( hWnd, uMsg, wParam, lParam, pProgress->_pData );
						}
					}
				}
				__except(GetExceptionCode()==EXCEPTION_ACCESS_VIOLATION ? 
							EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) 
				{
				  // code to handle exception
				  puts("Exception Occurred");
				}
				return 0;
			}

		public:

			virtual HWND	Create			( LPCTSTR szTitle, PUpdCallback pFnCallBack ) = 0;
			virtual VOID	Destroy			( VOID ) {};
			virtual VOID	SetTitle		( LPCTSTR szText ) = 0;
			virtual VOID	SetText			( LPCTSTR szText ) = 0;

			BOOLEAN IsVisible() const
			{
				return _hWnd != NULL;
			}
			/**
			 *
			 *
			 **/
			BOOLEAN IsCanceled() const
			{
				return _bIsCanceled;
			}

			/**
			 *
			 *
			 **/
			VOID SetTitleParam( LPCTSTR szText, ... )
			{
				if( szText )
				{
					TCHAR buf[MAXDATASIZE];
					va_list vl;
					
					va_start( vl, szText );
					if( mir_vsntprintf( buf, SIZEOF( buf ), szText, vl ) != -1 )
					{
						SetTitle( buf );   
					}
					va_end( vl );
				}
			}

			/**
			 * This method is used to set the popups or dialogs message text.
			 * It takes text with parameters as sprintf does. If bbcodes are
			 * disabled this method automatically deletes them from the text. 
			 *
			 * @param		szText			- the text to display. Can contain formats like
			 *								  sprintf does.
			 * @param		...				- a list of parameters, which depends on the
			 *								  format of szText.
			 *
			 * @return	nothing
			 **/
			VOID SetTextParam( LPCTSTR szText, ... ){
				if( szText ){
					const INT cch = _tcslen( szText );
					LPTSTR		fmt = ( LPTSTR ) mir_alloc( ( cch + 1 ) * sizeof( TCHAR ) );
					
					if( fmt ){
						TCHAR buf[MAXDATASIZE];
						va_list vl;

						_tcscpy( fmt, szText );

						// delete bbcodes
						if( !_bBBCode ){
							LPTSTR s, e;

							for( s = fmt, e = fmt + cch; s[0] != 0; s++ ){
								if( s[0] == '[' ){
									// leading bbcode tag (e.g.: [b], [u], [i])
									if( ( s[1] == 'b' || s[1] == 'u' || s[1] == 'i' ) && s[2] == ']' ){
										memmove( s, s + 3, ( e - s - 2 ) * sizeof( TCHAR ) );
										e -= 3;
									}
									// ending bbcode tag (e.g.: [/b], [/u], [/i])
									else if( s[1] == '/' && ( s[2] == 'b' || s[2] == 'u' || s[2] == 'i' ) && s[3] == ']' ){
										memmove( s, s + 4, ( e - s - 3 ) * sizeof( TCHAR ) );
										e -= 4;
									}
								}
							}
						}
					
						va_start( vl, szText );
						if( mir_vsnprintfT( buf, SIZEOF( buf ), fmt, vl ) != -1 ){
							SetText( buf );   
						}
						va_end( vl );
						mir_free( fmt );
					}
				}
			}

			/**
			 *
			 *
			 **/
			CUpdProgress()
			{
				_hWnd = NULL;
				_pFnCallBack = NULL;
				_pData = NULL;
				_bIsCanceled = FALSE;
				_bBBCode = FALSE;
			}

			/**
			 *
			 *
			 **/
			CUpdProgress( PVOID data )
			{
				_hWnd = NULL;
				_pFnCallBack = NULL;
				_pData = data;
				_bIsCanceled = FALSE;
				_bBBCode = FALSE;
			}

			~CUpdProgress()
			{
			}

		};

		/***********************************************************************************************************
		 * class CDlgUpdProgress
		 ***********************************************************************************************************/

		class CDlgUpdProgress : public CUpdProgress
		{
			/**
			 *
			 *
			 **/
			static LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) 
			{
				switch( uMsg )
				{
				case WM_INITDIALOG:
					{
						const NIcoLib::ICONCTRL idIcon[] = {
							{ ICO_BTN_UPDATE,		WM_SETICON,		NULL		},
							{ ICO_BTN_DOWNARROW,	BM_SETIMAGE,	IDSKIP		},
							{ ICO_BTN_CANCEL,		BM_SETIMAGE,	IDCANCEL	}
						};
						NIcoLib::SetCtrlIcons( hWnd, idIcon, DB::Setting::GetByte( SET_ICONS_BUTTONS, 1 ) ? 3 : 1 );
			
						SendDlgItemMessage( hWnd, IDCANCEL, BUTTONTRANSLATE, NULL, NULL );
						SendDlgItemMessage( hWnd, IDSKIP, BUTTONTRANSLATE, NULL, NULL );
						SetUserData( hWnd, lParam );
						
						TranslateDialogDefault( hWnd );
					}
					return TRUE;

				case WM_CTLCOLORSTATIC:
					{
						switch( GetWindowLong( ( HWND )lParam, GWL_ID ) ) {
							case STATIC_WHITERECT:
							case IDC_INFO:
								{
									SetTextColor( ( HDC )wParam, GetSysColor( COLOR_WINDOWTEXT ) );
									return GetSysColor( COLOR_WINDOW );
								}
						}
					}
					return FALSE;

				}
				return CUpdProgress::DefWndProc( (CUpdProgress *) GetUserData( hWnd ), hWnd, uMsg, wParam, lParam );
			}

		public:

			/**
			 *
			 *
			 **/
			CDlgUpdProgress( PVOID data )
				: CUpdProgress( data )
			{
			}

			/**
			 *
			 *
			 **/
			virtual HWND Create( LPCTSTR szTitle, PUpdCallback pFnCallBack )
			{
				_pFnCallBack = pFnCallBack;
				_hWnd = CreateDialogParam( ghInst, 
									MAKEINTRESOURCE( IDD_REFRESHDETAILS ), 
									0, 
									( DLGPROC )CDlgUpdProgress::WndProc, 
									( LPARAM ) this );
				if( _hWnd )
				{
					SetTitle( szTitle );
					ShowWindow( _hWnd, SW_SHOW );
				}
				return _hWnd;
			}

			/**
			 *
			 *
			 **/
			virtual VOID Destroy()
			{
				if( _hWnd )
				{
					SetUserData( _hWnd, NULL );
					EndDialog( _hWnd, IDOK );
					_hWnd = NULL;
				}
			}

			/**
			 *
			 *
			 **/
			virtual VOID SetTitle( LPCTSTR szText )
			{
				SetWindowText( _hWnd, szText );
			}

			/**
			 *
			 *
			 **/
			virtual VOID SetText( LPCTSTR szText )
			{
				SetDlgItemText( _hWnd, IDC_INFO, szText );
			}

		};

		/***********************************************************************************************************
		 * class CPopupUpdProgress
		 ***********************************************************************************************************/

		class CPopupUpdProgress : public CUpdProgress
		{
			LPCTSTR			_szText;
			POPUPACTION		_popupButtons[2];

			/**
			 *
			 *
			 **/
			VOID UpdateText()
			{
				if( _szText )
				{
					INT		 cb = mir_tcslen( _szText ) + 8;
					LPTSTR pb = ( LPTSTR ) mir_alloc( cb * sizeof( TCHAR ) );

					if( pb )
					{
						mir_tcscpy( pb, _szText );

						SendMessage( _hWnd, UM_CHANGEPOPUP, CPT_TITLET, ( LPARAM ) pb );
					}
				}
			}

			/**
			 * This static member is the window procedure, which is used to modify the behaviour of
			 * a popup dialog, so that it can act as a replacement for the progress dialog.
			 * The major task of this method is to filter out some messages, who would lead to a crash,
			 * if passed to the default windows procedure.
			 *
			 **/
			static LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) 
			{
				// Filter out messages, which must not be passed to default windows procedure or even
				// to the callback function!
				switch( uMsg )
				{
				case UM_INITPOPUP:
				case UM_CHANGEPOPUP:
				case UM_FREEPLUGINDATA:
					break;
				default:
					CUpdProgress::DefWndProc( (CUpdProgress *) PUGetPluginData( hWnd ), hWnd, uMsg, wParam, lParam );
				}
				return DefWindowProc( hWnd, uMsg, wParam, lParam );
			}

		public:

			/**
			 *
			 *
			 **/
			CPopupUpdProgress( PVOID data )
				: CUpdProgress( data )
			{
				_szText = NULL;
				_bBBCode = DB::Setting::GetByte( "PopUp", "UseMText", FALSE );

				_popupButtons[0].cbSize = sizeof( POPUPACTION );
				_popupButtons[0].flags = PAF_ENABLED;
				_popupButtons[0].lchIcon = NIcoLib::GetIcon( ICO_BTN_DOWNARROW );
				_popupButtons[0].wParam = MAKEWORD( IDSKIP, BN_CLICKED );
				_popupButtons[0].lParam = NULL;
				strcpy( _popupButtons[0].lpzTitle, MODNAME"/Hide" );

				// cancel button
				_popupButtons[1].cbSize = sizeof( POPUPACTION );
				_popupButtons[1].flags = PAF_ENABLED;
				_popupButtons[1].lchIcon = NIcoLib::GetIcon( ICO_BTN_CANCEL );
				_popupButtons[1].wParam = MAKEWORD( IDCANCEL, BN_CLICKED );
				_popupButtons[1].lParam = NULL;
				strcpy( _popupButtons[1].lpzTitle, MODNAME"/Cancel" );
			}

			/**
			 *
			 *
			 **/
			virtual HWND Create( LPCTSTR szTitle, PUpdCallback pFnCallBack )
			{
				POPUPDATAT	pd;
				
				ZeroMemory( &pd, sizeof( POPUPDATAT ) );
				pd.cbSize = sizeof( POPUPDATAT );
				pd.lchIcon = NIcoLib::GetIcon( ICO_BTN_UPDATE );
				pd.iSeconds = -1;
				pd.PluginData = this;
				pd.PluginWindowProc = ( WNDPROC )CPopupUpdProgress::WndProc;
				pd.actionCount = SIZEOF( _popupButtons );
				pd.lpActions = _popupButtons;

				// dummy text
				_szText = mir_tcsdup( szTitle );
				mir_tcscpy( pd.lptzContactName, _szText );
				
				_tcscpy( pd.lptzText, _T(" ") );
				
				_pFnCallBack = pFnCallBack;
				_hWnd = ( HWND ) CallService(MS_POPUP_ADDPOPUPT, ( WPARAM ) &pd, APF_RETURN_HWND|APF_NEWDATA );
				return _hWnd;
			}

			/**
			 *
			 *
			 **/
			virtual VOID Destroy()
			{
				if( _hWnd )
				{
					PUDeletePopUp( _hWnd );
					_hWnd = NULL;
				}
				MIR_FREE( _szText );
			}

			/**
			 *
			 *
			 **/
			virtual VOID SetTitle( LPCTSTR szText )
			{
				MIR_FREE( _szText );
				_szText = mir_tcsdup( szText );
				UpdateText();
			}

			/**
			 *
			 *
			 **/
			virtual VOID SetText( LPCTSTR szText )
			{
				SendMessage( _hWnd, UM_CHANGEPOPUP, CPT_TEXTT, ( LPARAM ) mir_tcsdup( szText ) );
			}
		};


		/***********************************************************************************************************
		 * class CContactUpdater
		 ***********************************************************************************************************/

		class CContactUpdater : public CContactQueue
		{
			CUpdProgress*	_pProgress;			// pointer to the progress dialog/popup
			HANDLE			_hProtoAckEvent;	// handle to protocol ack notification
			HANDLE			_hContact;			// current contact being refreshed
			PBYTE			_hContactAcks;		// array of acknoledgements for the current contact to wait for
			INT				_nContactAcks;		// number of acknoledgements for the current contact to wait for

			MIRANDA_CPP_PLUGIN_API( CContactUpdater );

			/**
			 * This is a callback dialog procedure, which is assigned the update progress dialog to
			 * gain control over certain messages.
			 *
			 * @param		hWnd		- HWND window handle of the progress dialog
			 * @param		uMsg		- message sent to the dialog
			 * @param		wParam		- message specific parameter
			 * @param		lParam		- message specific parameter
			 * @param		u			- This is a parameter assigned by the CUpdProgress' constructur.
			 *							  In this case it is a pointer to this class's object.
			 *
			 * @return	This method returns 0.
			 **/
			static INT DlgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, CContactUpdater* u ) 
			{
				switch( uMsg ) 
				{

				/**
				 * User has clicked on the skip or cancel button.
				 **/
				case UM_POPUPACTION:
				case WM_COMMAND: 
					{
						if( PtrIsValid( u ) )
						{
							switch( LOWORD( wParam ) )
							{
							case IDCANCEL:
								{
									if( HIWORD( wParam ) == BN_CLICKED ) 
									{
										u->Cancel();
									}
								}
							}
						}
					}
				}
				return 0;
			}

			/**
			 * This method handles ack broadcasts from the protocols to determine,
			 * whether a contact's information set's update is complete to continue
			 * with the next faster. By default the queue is configured to wait
			 * about 15s between contacts. If the protocol sends a complete ack,
			 * the time is shortend to 4s.
			 *
			 * @param		wParam		- not used
			 * @param		ack			- pointer to an ACKDATA structure containing all 
			 *							  data for the acknoledgement.
			 *
			 * @return	nothing
			**/
			INT __cdecl OnProtoAck( WPARAM wParam, ACKDATA *ack )
			{
				if( ack && ack->cbSize == sizeof( ACKDATA ) && ack->hContact == _hContact && ack->type == ACKTYPE_GETINFO )
				{
					if( ack->hProcess || ack->lParam ) 
					{
						if( !_hContactAcks )
						{
							_nContactAcks = ( INT )ack->hProcess;
							_hContactAcks = ( PBYTE )mir_calloc( sizeof( BYTE ) * ( INT )ack->hProcess );
						}
						if( ack->result == ACKRESULT_SUCCESS || ack->result == ACKRESULT_FAILED )
						{
							_hContactAcks[ack->lParam] = 1;
						}

						for( INT i = 0; i < _nContactAcks; i++ )
						{
							if( _hContactAcks[i] == 0 )
							{
								return 0;
							}
						}
					}
					// don't wait the full time, but continue immitiatly
					ContinueWithNext();
				}
				return 0;
			}

			/**
			 * This method is called just before the worker thread is going to suspend,
			 * if the queue is empty.
			 *
			 * @param		none
			 *
			 * @return	nothing
			 **/
			virtual VOID OnEmpty() 
			{
				// This was the last contact, so destroy the progress window.
				if( _hProtoAckEvent )
				{
					UnhookEvent( _hProtoAckEvent );
					_hProtoAckEvent = NULL;
				}

				// free up last ackresult array
				MIR_FREE( _hContactAcks );
				_nContactAcks = 0;
				_hContact = NULL;

				// close progress bar
				if( _pProgress )
				{
					_pProgress->Destroy();

					delete _pProgress;
					_pProgress = NULL;
				}

				// reset menu
				if( MenuItems::hMenuItemRefresh )
				{
					CLISTMENUITEM clmi;

					clmi.cbSize = sizeof( CLISTMENUITEM );
					clmi.flags = CMIM_NAME|CMIM_ICON;
					clmi.pszName = LPGEN( "Refresh Contact Details" );
					clmi.hIcon = NIcoLib::GetIcon( ICO_BTN_UPDATE );
					CallService( MS_CLIST_MODIFYMENUITEM,	( WPARAM )MenuItems::hMenuItemRefresh, ( LPARAM )&clmi );
				}
			}

			/**
			 * This virtual method is called by the derived CContactQueue class,
			 * if an action is requested for a queue item.
			 *
			 * @param		hContact	- the handle of the contact an action is requested for
			 * @param		param		- not used here
			 *
			 * @return	nothing
			 **/
			virtual VOID Callback( HANDLE hContact, PVOID param )
			{
				LPSTR	szProto	= DB::Contact::Proto( hContact );

				if( szProto && szProto[0] )
				{
					MIR_FREE( _hContactAcks );
					_nContactAcks = 0;
					_hContact = hContact;

					if( !_hProtoAckEvent )
					{
						_hProtoAckEvent = ( HANDLE ) ThisHookEvent( ME_PROTO_ACK, ( EVENTHOOK ) &CContactUpdater::OnProtoAck );
					}

					if( _pProgress )
					{
						_pProgress->SetTextParam(TranslateT( "[b]%s (%S)...[/b]\n%d Contacts remaning"),
							DB::Contact::DisplayName( _hContact ), szProto, Size() );
					}
					if( IsProtoOnline( szProto ) )
					{
						INT i;

						for( i = 0; i < 3 && CallContactService( hContact, PSS_GETINFO, 0, 0 ); i++ )
						{
							Sleep( 3000 );
						}
					}
				}
			}

		public:

			/**
			 * This is the default constructor
			 *
			 **/
			CContactUpdater() : CContactQueue()
			{
				_hContactAcks = NULL;
				_nContactAcks = 0;
				_hContact = NULL;
				_pProgress = NULL;
				_hProtoAckEvent = NULL;
			}

			/**
			 *
			 *
			 **/
			~CContactUpdater()
			{
				OnEmpty();
			}

			/**
			 *
			 *
			 **/
			BOOL QueueAddRefreshContact( HANDLE hContact, INT iWait )
			{
				LPSTR szProto = DB::Contact::Proto( hContact );
				CHAR szService[MAXMODULELABELLENGTH + sizeof( PSS_GETINFO ) + 1]="";

				if( (mir_strcmp( szProto, "Weather" )!=0) && 
					(mir_strcmp( szProto, "MetaContacts" )!=0) && 
						IsProtoOnline( szProto )) //&&
					//	ServiceExists( strcat( mir_strncpy( szService, szProto, MAXMODULELABELLENGTH ), PSS_GETINFO ) ) )
				{
					return Add( iWait, hContact );
				}
				return 0;
			}

			/**
			 *
			 *
			 **/
			VOID RefreshAll()
			{
				HANDLE		hContact;
				INT			iWait;

				for( hContact = DB::Contact::FindFirst(),	iWait = 100;
						 hContact != NULL;
						 hContact = DB::Contact::FindNext( hContact ) )
				{
					if( QueueAddRefreshContact( hContact, iWait ) )
					{
						iWait += 15000;
					}
				}
				if( Size() && !_pProgress )
				{
					if( ServiceExists( MS_POPUP_CHANGETEXTT ) && DB::Setting::GetByte( "PopupProgress", FALSE ) )
					{
						_pProgress = new CPopupUpdProgress( this );
					}
					else
					{
						_pProgress = new CDlgUpdProgress( this );
					}

					_pProgress->Create( TranslateT( "Refresh Contact Details" ), ( PUpdCallback ) CContactUpdater::DlgProc );
					_pProgress->SetText( TranslateT( "Preparing..." ) );
				}

				// if there are contacts in the queue, change the main menu item to indicate it is meant for canceling.
				if( MenuItems::hMenuItemRefresh && Size() > 0 )
				{
					CLISTMENUITEM clmi;

					clmi.cbSize = sizeof( CLISTMENUITEM );
					clmi.flags = CMIM_NAME|CMIM_ICON;
					clmi.pszName = LPGEN( "Abort Refreshing Contact Details" );
					clmi.hIcon = NIcoLib::GetIcon( ICO_BTN_CANCEL );
					CallService( MS_CLIST_MODIFYMENUITEM,	( WPARAM )MenuItems::hMenuItemRefresh, ( LPARAM )&clmi );
				}
			}

			/**
			 *
			 *
			 **/
			VOID Cancel()
			{
				RemoveAll();
				ContinueWithNext();
			}

		};
		
		static CContactUpdater	*ContactUpdater = NULL;

		/***********************************************************************************************************
		 * common helper functions
		 ***********************************************************************************************************/
	
		/**
		 * This function checks, whether at least one protocol is online!
		 *
		 * @param	none
		 *
		 * @retval	TRUE	- At least one protocol is online.
		 * @retval	FALSE	- All protocols are offline.
		 **/
		static BOOL IsMirandaOnline()
		{
			PROTOCOLDESCRIPTOR **pd;
			INT ProtoCount, i;

			if( !CallService( MS_PROTO_ENUMPROTOCOLS, ( WPARAM )&ProtoCount, ( LPARAM )&pd ) )
			{
				CHAR szService[MAXMODULELABELLENGTH + sizeof( PSS_GETINFO ) + 1]="";
				
				for( i = 0; i < ProtoCount; i++ ) 
				{
					if( pd[i]->type == PROTOTYPE_PROTOCOL && IsProtoOnline( pd[i]->szName ) )//&&  
							//ServiceExists( strcat( mir_strncpy( szService, pd[i]->szName, MAXMODULELABELLENGTH ), PSS_GETINFO ) ) )
					{
						return TRUE;
					}
				}
			}
			return FALSE;
		}

		/***********************************************************************************************************
		 * services
		 ***********************************************************************************************************/

		/**
		 * This is the service function being called by MS_USERINFO_REFRESH.
		 * It adds each contact, whose protocol is online, to the queue of contacts to refresh.
		 * The queue is running a separate thread, which is responsible for requesting the contact information
		 * one after another with a certain time to wait in between.
		 *
		 * @param	wParam	- not used
		 * @param	lParam	- not used
		 *
		 * @return	This service function always returns 0.
		 **/
		static INT RefreshService( WPARAM wParam, LPARAM lParam )
		{
			try
			{
				if( IsMirandaOnline() )
				{
					if( !ContactUpdater )
					{
						ContactUpdater = new CContactUpdater();
					}

					if( ContactUpdater->Size() == 0 )
					{
						ContactUpdater->RefreshAll();
					}
					else if( IDYES == MsgBox( NULL, MB_YESNO|MB_ICON_QUESTION, LPGENT( "Refresh Contact Details" ), NULL, 
						LPGENT( "Do you want to cancel the current refresh procedure?" ) ) )
					{
						ContactUpdater->Cancel();
					}
				}
				else
				{
					MsgErr( NULL, LPGENT( "Miranda must be online for refreshing contact information!" ) );
				}
			}
			catch( ... )
			{
				MsgErr( NULL, LPGENT( "The function caused an exception!" ) );
			}
			return 0;
		}

		/***********************************************************************************************************
		 * events
		 ***********************************************************************************************************/

		/**
		 *
		 *
		 **/
		static INT OnContactAdded( WPARAM wParam, LPARAM lParam )
		{
			try
			{
				DWORD dwStmp;

				dwStmp = DB::Setting::GetDWord( ( HANDLE )wParam, USERINFO, SET_CONTACT_ADDEDTIME, 0 );
				if( !dwStmp )
				{
					MTime mt;
					
					mt.GetLocalTime();
					mt.DBWriteStamp( ( HANDLE )wParam, USERINFO, SET_CONTACT_ADDEDTIME );

					// create updater, if not yet exists
					if( !ContactUpdater )
					{
						ContactUpdater = new CContactUpdater();
					}
					
					// add to the end of the queue
					ContactUpdater->AddIfDontHave( 
						( ContactUpdater->Size() > 0 ) 
							? max( ContactUpdater->Get( ContactUpdater->Size() - 1 )->check_time + 15000, 4000 ) 
							: 4000,
						( HANDLE )wParam
					);
				}
			}
			catch( ... )
			{
				MsgErr( NULL, LPGENT( "The function caused an exception!" ) );
			}
			return 0;
		}

		/**
		 * Miranda is going to shutdown soon, so any panding contact information refresh is to be terminated
		 * and the queue object must be deleted. Further refresh requests will be ignored.
		 *
		 * @param		wParam	- not used
		 * @param		lParam	- not used
		 *
		 * @return	This function always returns 0.
		 **/
		static INT OnPreShutdown( WPARAM, LPARAM )
		{
			MIR_DELETE( ContactUpdater );
			return 0;
		}

		/***********************************************************************************************************
		 * initialization
		 ***********************************************************************************************************/

		/**
		 * This function initially loads the module uppon startup.
		 **/
		VOID LoadModule( VOID )
		{
			HOTKEYDESC hk;

			CreateServiceFunction( MS_USERINFO_REFRESH, RefreshService );
			HookEvent(ME_SYSTEM_PRESHUTDOWN, OnPreShutdown );
			HookEvent( ME_DB_CONTACT_ADDED, OnContactAdded );

			hk.cbSize = sizeof( HOTKEYDESC );
			hk.lParam = NULL;
			hk.DefHotKey = NULL;
			hk.pszSection = MODNAME;
			hk.pszName = "RefreshContactDetails";
			hk.pszDescription = LPGEN( "Refresh Contact Details" );
			hk.pszService = MS_USERINFO_REFRESH;
			CallService( MS_HOTKEY_REGISTER, NULL, ( LPARAM )&hk );
		}

	} /* namespace RefreshInfo */

} /* namespace NServices */
